package com.my.spring.framework.context;

import com.my.spring.framework.annotation.MyAutowired;
import com.my.spring.framework.annotation.MyController;
import com.my.spring.framework.annotation.MyService;
import com.my.spring.framework.aop.MyAutoProxyCreator;
import com.my.spring.framework.aop.config.MyAopConfig;
import com.my.spring.framework.aop.support.MyAdvisedSupport;
import com.my.spring.framework.beans.MyBeanWrapper;
import com.my.spring.framework.beans.MyObjectFactory;
import com.my.spring.framework.beans.config.MyBeanDefinition;
import com.my.spring.framework.beans.config.MyBeanPostProcessor;
import com.my.spring.framework.beans.support.MyBeanDefinitionReader;
import com.my.spring.framework.context.support.MyAbstractApplicationContext;
import com.my.spring.framework.context.support.MyApplicationContextAwareProcessor;
import com.my.spring.framework.utils.BeanNameUtil;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MyApplicationContext extends MyAbstractApplicationContext {
    private String[] configLocations;
    private MyBeanDefinitionReader reader;
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    private final Map<String, MyBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(256);

    private List<MyBeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public MyApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void refresh() throws Exception {
        prepareBeanFactory();

        // 定位配置文件
         reader = new MyBeanDefinitionReader(configLocations);

        // 加载配置文件，扫描相关类成BeanDefinition
        List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        // 注册，把配置信息放到ioc容器
        doRegisterBeanDefinitions(beanDefinitions);

        // 注册后置处理器
        registerBeanPostProcessors(beanDefinitions);

        // 非懒加载的类提前初始化
        doInitializeBeans();
    }

    private void registerBeanPostProcessors(List<MyBeanDefinition> beanDefinitions) {
        try {
            Set<String> registered = new HashSet<>();
            for (MyBeanDefinition beanDefinition : beanDefinitions) {
                String className = beanDefinition.getBeanClassName();
                if (registered.contains(className)) {
                    continue;
                }
                registered.add(className);
                Class<?> beanClass = Class.forName(className);
                if (MyBeanPostProcessor.class.isAssignableFrom(beanClass)) {
                    MyBeanPostProcessor instance = (MyBeanPostProcessor) beanClass.getConstructor().newInstance();
                    beanPostProcessors.add(instance);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doInitializeBeans() {
        beanDefinitionMap.forEach((k, v) -> {
            if (!v.isLazyInit()) {
                try {
                    getBean(k);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void doRegisterBeanDefinitions(List<MyBeanDefinition> beanDefinitions) throws Exception {
        for (MyBeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("bean " + beanDefinition.getBeanClassName() + "exists");
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }


    @Override
    public Object getBean(String beanName) throws Exception {
        MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new Exception("BeanDefinition of " + beanName + "does not exist");
        }
        return getSingleton(beanName, () -> createBean(beanName, beanDefinition));
    }

    private Object getSingleton(String beanName, MyObjectFactory<?> objectFactory) throws Exception {
        Object bean = singletonObjects.get(beanName);
        if (bean != null) {
            return bean;
        }
        return objectFactory.getObject();
    }

    @Override
    public Object createBean(String beanName, MyBeanDefinition beanDefinition) throws Exception {
        // 实例化
        MyBeanWrapper beanWrapper = instantiateBean(beanName, beanDefinition);

        factoryBeanInstanceCache.put(beanName, beanWrapper);

        // 填充字段
        populateBean(beanName, beanDefinition, beanWrapper);

        // 初始化
        Object exposedObject = beanWrapper.getWrappedInstance();
        exposedObject = initializeBean(beanName, exposedObject, beanDefinition);

        // Register bean as disposable.

        return exposedObject;
    }

    private Object initializeBean(String beanName, Object bean, MyBeanDefinition beanDefinition) throws Exception {
        // aware
        invokeAwareMethods(beanName, bean);

        // beforeInit
        Object wrappedBean = bean;
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

        // initMethod
        invokeInitMethods(beanName, bean, beanDefinition);

        // afterInit
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        return wrappedBean;
    }

    private Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws Exception {
        Object result = existingBean;
        for (MyBeanPostProcessor processor : beanPostProcessors) {
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    private void invokeInitMethods(String beanName, Object bean, MyBeanDefinition beanDefinition) {
    }

    private Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws Exception {
        Object result = existingBean;
        for (MyBeanPostProcessor processor : beanPostProcessors) {
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    private void invokeAwareMethods(String beanName, Object bean) {
    }

    private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
        Class<?> clazz = beanWrapper.getWrappedClass();
        if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
            return;
        }
        Object instance = beanWrapper.getWrappedInstance();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(MyAutowired.class)) {
                continue;
            }

            MyAutowired autowired = field.getAnnotation(MyAutowired.class);
            String autoWiredBeanName = autowired.value().trim();
            if ("".equals(autoWiredBeanName)) {
                autoWiredBeanName = BeanNameUtil.getBeanName(field.getType());
            }

            field.setAccessible(true);
            try {
                Object wiredBean = getBean(autoWiredBeanName);
                field.set(instance, wiredBean);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private MyBeanWrapper instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if (singletonObjects.containsKey(className)) {
                instance = singletonObjects.get(className);
            } else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.getConstructor().newInstance();
                singletonObjects.put(beanDefinition.getFactoryBeanName(), instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
        return beanWrapper;
    }

    private MyAdvisedSupport instantiateAopConfig(MyBeanDefinition beanDefinition) {
        MyAopConfig config = new MyAopConfig();
        config.setPointCut(reader.getConfig().getProperty("pointCut"));
        config.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));
        config.setAspectClass(reader.getConfig().getProperty("aspectClass"));
        return new MyAdvisedSupport(config);
    }

    void addBeanPostProcessor(MyBeanPostProcessor beanPostProcessor) {
        beanPostProcessors.add(beanPostProcessor);
    }
    protected void prepareBeanFactory() {
        addBeanPostProcessor(new MyApplicationContextAwareProcessor(this));
        addBeanPostProcessor(new MyAutoProxyCreator(this)); // 这个spring中不是在这里注册的
    }


    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }

    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    public Properties getConfig() {
        return reader.getConfig();
    }
}
